module dares.Dns;

import dares.Types;

enum{
	NS_QFIXEDSZ	= 4 ,
	QFIXEDSZ		= NS_QFIXEDSZ ,
	NS_RRFIXEDSZ	= 10  ,
	RRFIXEDSZ		= NS_RRFIXEDSZ ,
}
enum  {
    ns_c_invalid = 0,       /* Cookie. */
    ns_c_in = 1,            /* Internet. */
    ns_c_2 = 2,             /* unallocated/unsupported. */
    ns_c_chaos = 3,         /* MIT Chaos-net. */
    ns_c_hs = 4,            /* MIT Hesiod. */
    /* Query class values which do not appear in resource records */
    ns_c_none = 254,        /* for prereq. sections in update requests */
    ns_c_any = 255,         /* Wildcard match. */
    ns_c_max = 65536
}

enum  {
    ns_t_invalid = 0,       /* Cookie. */
    ns_t_a = 1,             /* Host address. */
    ns_t_ns = 2,            /* Authoritative server. */
    ns_t_md = 3,            /* Mail destination. */
    ns_t_mf = 4,            /* Mail forwarder. */
    ns_t_cname = 5,         /* Canonical name. */
    ns_t_soa = 6,           /* Start of authority zone. */
    ns_t_mb = 7,            /* Mailbox domain name. */
    ns_t_mg = 8,            /* Mail group member. */
    ns_t_mr = 9,            /* Mail rename name. */
    ns_t_null = 10,         /* Null resource record. */
    ns_t_wks = 11,          /* Well known service. */
    ns_t_ptr = 12,          /* Domain name pointer. */
    ns_t_hinfo = 13,        /* Host information. */
    ns_t_minfo = 14,        /* Mailbox information. */
    ns_t_mx = 15,           /* Mail routing information. */
    ns_t_txt = 16,          /* Text strings. */
    ns_t_rp = 17,           /* Responsible person. */
    ns_t_afsdb = 18,        /* AFS cell database. */
    ns_t_x25 = 19,          /* X_25 calling address. */
    ns_t_isdn = 20,         /* ISDN calling address. */
    ns_t_rt = 21,           /* Router. */
    ns_t_nsap = 22,         /* NSAP address. */
    ns_t_nsap_ptr = 23,     /* Reverse NSAP lookup (deprecated). */
    ns_t_sig = 24,          /* Security signature. */
    ns_t_key = 25,          /* Security key. */
    ns_t_px = 26,           /* X.400 mail mapping. */
    ns_t_gpos = 27,         /* Geographical position (withdrawn). */
    ns_t_aaaa = 28,         /* Ip6 Address. */
    ns_t_loc = 29,          /* Location Information. */
    ns_t_nxt = 30,          /* Next domain (security). */
    ns_t_eid = 31,          /* Endpoint identifier. */
    ns_t_nimloc = 32,       /* Nimrod Locator. */
    ns_t_srv = 33,          /* Server Selection. */
    ns_t_atma = 34,         /* ATM Address */
    ns_t_naptr = 35,        /* Naming Authority PoinTeR */
    ns_t_kx = 36,           /* Key Exchange */
    ns_t_cert = 37,         /* Certification record */
    ns_t_a6 = 38,           /* IPv6 address (deprecates AAAA) */
    ns_t_dname = 39,        /* Non-terminal DNAME (for IPv6) */
    ns_t_sink = 40,         /* Kitchen sink (experimentatl) */
    ns_t_opt = 41,          /* EDNS0 option (meta-RR) */
    ns_t_apl = 42,          /* Address prefix list (RFC3123) */
    ns_t_tkey = 249,        /* Transaction key */
    ns_t_tsig = 250,        /* Transaction signature. */
    ns_t_ixfr = 251,        /* Incremental zone transfer. */
    ns_t_axfr = 252,        /* Transfer zone of authority. */
    ns_t_mailb = 253,       /* Transfer mailbox records. */
    ns_t_maila = 254,       /* Transfer mail agent records. */
    ns_t_any = 255,         /* Wildcard match. */
    ns_t_zxfr = 256,        /* BIND-specific, nonstandard. */
    ns_t_max = 65536
}



T RevertByte(T)(T v) {
	static assert(  isIntegerType!(T) );
	T _v		= v;
	T*	p	=  cast(T*) &( (cast(ubyte*) &_v)[ 0 .. T.sizeof ] .reverse )[0] ;
	return	*p ;
}

template DNS_Parser(alias T) {
	static const _TName	= T.stringof ;
	
	static T* load(ubyte* abuf, int alen, ref ubyte* aptr, ref ubyte* __aptr ){
		T*	p 		= new T ;
		ubyte*	_aptr = aptr ;
		__aptr		= aptr ;
		foreach( int i, f; (*p).tupleof ) {
			static const _FName	= T.tupleof[i].stringof[ _TName.length + 3 .. $ ] ;
			alias typeof(f) F ;
			
			static if(  isIntegerType!(F) ) {
				(*p).tupleof[i]	= RevertByte(  *(cast(F*) _aptr) ) ;
				_aptr	+=	F.sizeof ;
			}else static if( isStaticArrayType!(F) ) {
				alias BaseTypeOfArrays!(F) __f ;
				const __len	= staticArraySize!(F) * __f.sizeof ;
				memcpy( &(*p).tupleof[i][0] , _aptr, __len);
				_aptr			+=	__len ;
			}else static if( isDynamicArrayType!(F) ){
				alias BaseTypeOfArrays!(F) __f ;
				static if( is ( __f == char ) ) {
					int		len ;
					char*	name ;
					int	ret	= ares_expand_name(_aptr, abuf , alen, &name , &len);
					if (ret != ARES_SUCCESS){
						return null ;
					}
					(*p).tupleof[i]	= fromStringz( name ).dup ;
					_aptr			+=	len ;
					ares_free_string(name);
				}
			}else  static if( isPointerType!(F) ){
				static if( is ( F == void* ) ) {
					ubyte* _my_aptr ;
					switch(  p.Type ) {
						case ns_t_mx :
							(*p).tupleof[i]	= cast(void*) DNS_MX_Record.load(abuf, alen, _aptr, _my_aptr );
							break;
						case ns_t_a:
							(*p).tupleof[i]	= cast(void*) DNS_A_Record.load(abuf, alen, _aptr, _my_aptr );
							break;
						case ns_t_ns:
							(*p).tupleof[i]	= cast(void*) DNS_DN_Record.load(abuf, alen, _aptr, _my_aptr );
							break;
						case ns_t_cname:
							(*p).tupleof[i]	= cast(void*) DNS_DN_Record.load(abuf, alen, _aptr, _my_aptr );
							break;
						default:
							Stdout.formatln("{}.Type = {}", _TName, p.Type ).flush;
							assert(false);
					}
				}else {
					static assert(false, _TName  ~ "." ~ _FName ~ " (" ~ F.stringof ~ ") ");
				}
			}else static if( is(F == in_addr ) ||  is(F == ares_in6_addr) ){
				memcpy( &(*p).tupleof[i] , _aptr, F.sizeof );
				_aptr			+=	F.sizeof ;
			} else {
				static assert(false);
			}
			
		}
		aptr		= _aptr ;
		return p ;
	}
	
	void dump( char[] tab = "" ) {
		const _struct	= T.stringof ;
		Stdout.formatln("{}{}", tab, _struct );
		foreach( int i, f;  this.tupleof ) {
			Stdout.format("{}	{}:	", tab, T.tupleof[i].stringof[ _struct.length + 3 .. $] );
			static if( is( typeof(f) == in_addr ) ){
				Stdout.formatln("{}",  (cast(ubyte*)&f )[0..4] );
			}else{
				Stdout.formatln("{}", f);
			}
		}
	}
}

struct DNS_HEADER {
	ushort		id ;
	ubyte[2]		__flags ;	// QR	Opcode:4	AA	TC	RD	|	RA	Z	AD	CD	RCODE:4
	ushort		q ;
	ushort		an;
	ushort		ns ;
	ushort		ar ;
	
	mixin DNS_Parser!(DNS_HEADER);
}


struct DNS_QUESION {
	char[]	Name;
	ushort	Type;
	ushort	Class;
	
	mixin DNS_Parser!(DNS_QUESION);
}

struct DNS_RR_Record {
	ushort	length;
	char*	data;
}

struct DNS_A_Record	{
	in_addr		addr ;
	mixin DNS_Parser!(DNS_A_Record);
}

struct DNS_AAAA_Record {
	ares_in6_addr	addr ;
	mixin DNS_Parser!(DNS_AAAA_Record);
}

struct DNS_DN_Record {
	char[]	name;
	mixin DNS_Parser!(DNS_DN_Record);
}

struct DNS_SOA_Record {
	char*	mname;
	char*	rname;
	uint		serial;
	uint		refresh;
	uint		retry;
	uint		expire;
	uint		minimum;
}

struct DNS_HINFO_Record {
	char*	cpu ;
	char*	os ;
}

struct DNS_MINFO_Record {
	char *rmailbx;
	char *emailbx;
}

struct DNS_MX_Record {
	ushort	preference ;
	char[]	name ;
	mixin DNS_Parser!(DNS_MX_Record);
}

struct DNS_TXT_Record {
	uint		string_count ;
	char**	strings ;
}

struct DNS_WKS_Record {
	in_addr	addr;
	ubyte	protocol;
	uint		maplength;
	ubyte*	map;
}

struct DNS_RP_Record {
	char*	mailbox;
	char*	txtdname;
}

struct DNS_AFSDB_Record{
	uint		subtype;
	char*	hostname;
}

struct DNS_X25_Record {
	char*	psdn_address;
} 

struct DNS_ISDN_Record{
	char*	isdn_address;
	char*	subaddress;
} 

struct DNS_RT_Record {
	ushort	preference;
	char*	intermediate;
} 

struct DNS_LOC_Record {
	ubyte	__version;
	ubyte	size;
	ubyte	horizontal_precision;
	ubyte	vertical_precision;
	uint		latitude;
	uint		longitude;
	uint		altitude;
}

struct DNS_SRV_Record {
	ushort	priority;
	ushort	weight;
	ushort	port;
	char*	target;
}


struct DNS_RR {
	char[]	Name	;
	ushort	Type	;
	ushort	Class	;
	uint		ttl	;
	ushort	len 	;
	void*	ptr	;
	mixin DNS_Parser!(DNS_RR);
	/* 
	union {
		DNS_A_Record*			A;
		DNS_AAAA_Record*		AAAA;
		
		DNS_RR_Record*			DNSNULL;
		
		DNS_DN_Record*			NS;
		DNS_DN_Record*			MD;
		DNS_DN_Record*			MF;
		DNS_DN_Record*			CNAME;
		DNS_DN_Record*			MB;
		DNS_DN_Record*			MG;
		DNS_DN_Record*			MR;
		DNS_DN_Record*			PTR;
		
		DNS_SOA_Record*		SOA;
		
		DNS_WKS_Record*		WKS;
		DNS_HINFO_Record*		HINFO;
		DNS_MINFO_Record*		MINFO;
		DNS_MX_Record*			MX;
		DNS_TXT_Record*		TXT;
		DNS_RP_Record*			RP;
		DNS_AFSDB_Record*		AFSDB;
		DNS_X25_Record*		X25;
		DNS_ISDN_Record*		ISDN;
		DNS_RT_Record*			RT;
		DNS_LOC_Record*		LOC;
		DNS_SRV_Record*		SRV;
	}
	*/
	
	DNS_A_Record* A() {
		if( Type != ns_t_a ){
			return null ;
		}
		return cast(DNS_A_Record* ) ptr ;
	}
	
	DNS_DN_Record* DN() {
		if( Type !is ns_t_ns &&Type !is ns_t_cname ){
			return null ;
		}
		return cast(DNS_DN_Record* ) ptr ;
	}
	
	DNS_MX_Record* MX() {
		if( Type != ns_t_mx ){
			return null ;
		}
		return cast(DNS_MX_Record* ) ptr ;
	}
	
	
	void dump_ptr(char[] tab = "\t\t" ){
		switch( Type ){
			case ns_t_a:
					A.dump(tab);
				break;
			case ns_t_ns:
			case ns_t_cname:
					DN.dump(tab);
				break;
			case ns_t_mx:
					MX.dump(tab);
				break;
			default:
				Stdout.formatln("un define dump Type:{}", Type );
		}
	}
}


struct DNS_Query {
	DNS_HEADER*	h ;
	DNS_QUESION*[]	q ;
	DNS_RR*[]		an ;
	DNS_RR*[]		ns ;
	DNS_RR*[]		ar ;
	
	static DNS_Query* Load(ubyte* abuf, int alen) {
		DNS_Query*	p	= new DNS_Query ;
		
		ubyte*	aptr , _aptr  ;
		aptr		= abuf ;
		
		p.h	= DNS_HEADER.load(abuf, alen, aptr , _aptr ) ;
		if( p.h is null ){
			return null;
		}
	
		for( int i = 0; i < p.h.q ; i++ ){
			auto q	= DNS_QUESION.load( abuf, alen, aptr , _aptr  ) ;
			if( q is null ){
				return null ;
			}
			p.q	~= q ;
		}
		
		for( int i = 0; i < p.h.an ; i++ ){
			auto r 	= DNS_RR.load( abuf, alen, aptr , _aptr  ) ;	
			if( r is null ){
				return null ;
			}
			p.an	~= r ;
		}
	
		for( int i = 0; i < p.h.ns ; i++ ){
			auto r 	= DNS_RR.load( abuf, alen, aptr , _aptr  ) ;	
			if( r is null ){
				return null ;
			}
			p.ns	~= r ;
		}
		
		for( int i = 0; i < p.h.ar ; i++ ){
			auto r 	= DNS_RR.load( abuf, alen, aptr , _aptr  ) ;	
			if( r is null ){
				return null ;
			}
			p.ar	~= r ;
		}
		
		return	p;
	}
}


static assert(DNS_HEADER.sizeof is 12);
unittest {
	DNS_HEADER* dns	= new DNS_HEADER;
	test!("QR", 	"false")(dns);
	test!("QR", 	"true")(dns);
	test!("QR", 	"false")(dns);
	
	test!("Opcode",	"0b1001")(dns);
	test!("Opcode",	"0b1011")(dns);
	test!("Opcode",	"0b0001")(dns);
	test!("Opcode",	"0b1101")(dns);

	test!("AA", 	"false")(dns);
	test!("AA", 	"true")(dns);
	test!("AA", 	"false")(dns);
	
	test!("TC", 	"false")(dns);
	test!("TC", 	"true")(dns);
	test!("TC", 	"false")(dns);
	
	test!("RD", 		"false")(dns);
	test!("RD", 		"true")(dns);
	test!("RD", 		"false")(dns);
	
	
	test!("RA", 		"false")(dns);
	test!("RA", 		"true")(dns);
	test!("RA", 		"false")(dns);
	
	test!("Z", 		"true")(dns);
	test!("Z", 		"false")(dns);
	
	test!("AD", 	"true")(dns);
	test!("AD", 	"false")(dns);
	
	test!("CD", 	"true")(dns);
	test!("CD", 	"false")(dns);
	
	test!("RCODE", 	"0b0001")(dns);
	test!("RCODE", 	"0b0101")(dns);
	test!("RCODE", 	"0b1011")(dns);
	test!("RCODE", 	"0b1101")(dns);
}